/**
 * @file sm2_common.c
 * @brief Source file for common SM2 cryptographic operations.
 *
 * This source file contains implementations of common functions and utilities required for SM2 cryptographic operations.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <limits.h>
#include <time.h>
#include <ctype.h>
#include "sm2_common.h"

/**
 * @brief Rotates a 32-bit word to the left by a specified number of bits.
 *
 * This macro performs a left rotation on a 32-bit word by the specified number of bits.
 *
 * @param x The 32-bit word to be rotated.
 * @param n The number of bits to rotate the word to the left.
 *
 * @return The result of the left rotation.
 */
#define ROTL(x, n) (((x) << (n)) | ((x) >> (32 - (n))))

/**
 * @brief Applies the P0 transformation to a 32-bit word.
 *
 * This macro applies the P0 transformation, which is part of the SM3 algorithm, to a 32-bit word.
 *
 * @param x The 32-bit word to be transformed.
 *
 * @return The result of the P0 transformation.
 */
#define P0(x) ((x) ^ ROL32((x), 9) ^ ROL32((x), 17))

/**
 * @brief Applies the P1 transformation to a 32-bit word.
 *
 * This macro applies the P1 transformation, which is part of the SM3 algorithm, to a 32-bit word.
 *
 * @param x The 32-bit word to be transformed.
 *
 * @return The result of the P1 transformation.
 */
#define P1(x) ((x) ^ ROL32((x), 15) ^ ROL32((x), 23))

/**
 * @brief Applies the FF00 function to three 32-bit words.
 *
 * This macro applies the FF00 function, which is part of the SM3 algorithm, to three 32-bit words.
 *
 * @param x The first 32-bit word.
 * @param y The second 32-bit word.
 * @param z The third 32-bit word.
 *
 * @return The result of the FF00 function.
 */
#define FF00(x, y, z) ((x) ^ (y) ^ (z))

/**
 * @brief Applies the FF16 function to three 32-bit words.
 *
 * This macro applies the FF16 function, which is part of the SM3 algorithm, to three 32-bit words.
 *
 * @param x The first 32-bit word.
 * @param y The second 32-bit word.
 * @param z The third 32-bit word.
 *
 * @return The result of the FF16 function.
 */
#define FF16(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z)))

/**
 * @brief Applies the GG00 function to three 32-bit words.
 *
 * This macro applies the GG00 function, which is part of the SM3 algorithm, to three 32-bit words.
 *
 * @param x The first 32-bit word.
 * @param y The second 32-bit word.
 * @param z The third 32-bit word.
 *
 * @return The result of the GG00 function.
 */
#define GG00(x, y, z) ((x) ^ (y) ^ (z))

/**
 * @brief Applies the GG16 function to three 32-bit words.
 *
 * This macro applies the GG16 function, which is part of the SM3 algorithm, to three 32-bit words.
 *
 * @param x The first 32-bit word.
 * @param y The second 32-bit word.
 * @param z The third 32-bit word.
 *
 * @return The result of the GG16 function.
 */
#define GG16(x, y, z) ((((y) ^ (z)) & (x)) ^ (z))

/**
 * @brief Executes one round of the SM3 compression function.
 *
 * This macro executes one round of the SM3 compression function, updating the intermediate variables.
 *
 * @param A The first intermediate variable.
 * @param B The second intermediate variable.
 * @param C The third intermediate variable.
 * @param D The fourth intermediate variable.
 * @param E The fifth intermediate variable.
 * @param F The sixth intermediate variable.
 * @param G The seventh intermediate variable.
 * @param H The eighth intermediate variable.
 * @param xx The round number, used to select the appropriate FF and GG functions.
 *
 * @pre The variables A, B, C, D, E, F, G, H must be properly initialized.
 * @pre The variable j must be defined and incremented within the calling context.
 *
 * @post The variables A, B, C, D, E, F, G, H will be updated according to the SM3 compression function.
 */
#define R1(A, B, C, D, E, F, G, H, xx)                   \
	SS1 = ROL32((ROL32(A, 12) + E + K[j]), 7);           \
	SS2 = SS1 ^ ROL32(A, 12);                            \
	TT1 = FF##xx(A, B, C) + D + SS2 + (W[j] ^ W[j + 4]); \
	TT2 = GG##xx(E, F, G) + H + SS1 + W[j];              \
	B = ROL32(B, 9);                                     \
	H = TT1;                                             \
	F = ROL32(F, 19);                                    \
	D = P0(TT2);                                         \
	j++

/**
 * @brief Executes eight rounds of the SM3 compression function.
 *
 * This macro executes eight rounds of the SM3 compression function, updating the intermediate variables.
 *
 * @param A The first intermediate variable.
 * @param B The second intermediate variable.
 * @param C The third intermediate variable.
 * @param D The fourth intermediate variable.
 * @param E The fifth intermediate variable.
 * @param F The sixth intermediate variable.
 * @param G The seventh intermediate variable.
 * @param H The eighth intermediate variable.
 * @param xx The round number, used to select the appropriate FF and GG functions.
 *
 * @pre The variables A, B, C, D, E, F, G, H must be properly initialized.
 * @pre The variable j must be defined and incremented within the calling context.
 *
 * @post The variables A, B, C, D, E, F, G, H will be updated according to the SM3 compression function.
 */
#define R8(A, B, C, D, E, F, G, H, xx) \
	R1(A, B, C, D, E, F, G, H, xx);    \
	R1(H, A, B, C, D, E, F, G, xx);    \
	R1(G, H, A, B, C, D, E, F, xx);    \
	R1(F, G, H, A, B, C, D, E, xx);    \
	R1(E, F, G, H, A, B, C, D, xx);    \
	R1(D, E, F, G, H, A, B, C, xx);    \
	R1(C, D, E, F, G, H, A, B, xx);    \
	R1(B, C, D, E, F, G, H, A, xx)

/**
 * @brief The constant array K used in the SM3 compression function.
 *
 * This array contains 64 constant values used in the SM3 compression function.
 * These constants are defined in the SM3 standard.
 */
static uint32_t K[64] = {
	0x79cc4519U,
	0xf3988a32U,
	0xe7311465U,
	0xce6228cbU,
	0x9cc45197U,
	0x3988a32fU,
	0x7311465eU,
	0xe6228cbcU,
	0xcc451979U,
	0x988a32f3U,
	0x311465e7U,
	0x6228cbceU,
	0xc451979cU,
	0x88a32f39U,
	0x11465e73U,
	0x228cbce6U,
	0x9d8a7a87U,
	0x3b14f50fU,
	0x7629ea1eU,
	0xec53d43cU,
	0xd8a7a879U,
	0xb14f50f3U,
	0x629ea1e7U,
	0xc53d43ceU,
	0x8a7a879dU,
	0x14f50f3bU,
	0x29ea1e76U,
	0x53d43cecU,
	0xa7a879d8U,
	0x4f50f3b1U,
	0x9ea1e762U,
	0x3d43cec5U,
	0x7a879d8aU,
	0xf50f3b14U,
	0xea1e7629U,
	0xd43cec53U,
	0xa879d8a7U,
	0x50f3b14fU,
	0xa1e7629eU,
	0x43cec53dU,
	0x879d8a7aU,
	0x0f3b14f5U,
	0x1e7629eaU,
	0x3cec53d4U,
	0x79d8a7a8U,
	0xf3b14f50U,
	0xe7629ea1U,
	0xcec53d43U,
	0x9d8a7a87U,
	0x3b14f50fU,
	0x7629ea1eU,
	0xec53d43cU,
	0xd8a7a879U,
	0xb14f50f3U,
	0x629ea1e7U,
	0xc53d43ceU,
	0x8a7a879dU,
	0x14f50f3bU,
	0x29ea1e76U,
	0x53d43cecU,
	0xa7a879d8U,
	0x4f50f3b1U,
	0x9ea1e762U,
	0x3d43cec5U,
};
/**
 * @brief Compresses multiple blocks of data using the SM3 compression function.
 *
 */
void peace_sm3_compress_blocks(uint32_t digest[8], const uint8_t *data, size_t blocks)
{

	uint32_t digest_tmp[8];
	uint32_t W[68];
	uint32_t SS1;
	uint32_t SS2;
	uint32_t TT1;
	uint32_t TT2;
	int j;

	while (blocks--)
	{

		digest_tmp[0] = digest[0];
		digest_tmp[1] = digest[1];
		digest_tmp[2] = digest[2];
		digest_tmp[3] = digest[3];
		digest_tmp[4] = digest[4];
		digest_tmp[5] = digest[5];
		digest_tmp[6] = digest[6];
		digest_tmp[7] = digest[7];

		for (j = 0; j < 16; j++)
		{
			W[j] = GETU32(data + j * 4);
		}

		for (; j < 68; j++)
		{
			W[j] = P1(W[j - 16] ^ W[j - 9] ^ ROL32(W[j - 3], 15)) ^ ROL32(W[j - 13], 7) ^ W[j - 6];
		}

		j = 0;

		R8(digest_tmp[0], digest_tmp[1], digest_tmp[2], digest_tmp[3], digest_tmp[4], digest_tmp[5], digest_tmp[6], digest_tmp[7], 00);
		R8(digest_tmp[0], digest_tmp[1], digest_tmp[2], digest_tmp[3], digest_tmp[4], digest_tmp[5], digest_tmp[6], digest_tmp[7], 00);
		R8(digest_tmp[0], digest_tmp[1], digest_tmp[2], digest_tmp[3], digest_tmp[4], digest_tmp[5], digest_tmp[6], digest_tmp[7], 16);
		R8(digest_tmp[0], digest_tmp[1], digest_tmp[2], digest_tmp[3], digest_tmp[4], digest_tmp[5], digest_tmp[6], digest_tmp[7], 16);
		R8(digest_tmp[0], digest_tmp[1], digest_tmp[2], digest_tmp[3], digest_tmp[4], digest_tmp[5], digest_tmp[6], digest_tmp[7], 16);
		R8(digest_tmp[0], digest_tmp[1], digest_tmp[2], digest_tmp[3], digest_tmp[4], digest_tmp[5], digest_tmp[6], digest_tmp[7], 16);
		R8(digest_tmp[0], digest_tmp[1], digest_tmp[2], digest_tmp[3], digest_tmp[4], digest_tmp[5], digest_tmp[6], digest_tmp[7], 16);
		R8(digest_tmp[0], digest_tmp[1], digest_tmp[2], digest_tmp[3], digest_tmp[4], digest_tmp[5], digest_tmp[6], digest_tmp[7], 16);
		for (j = 0; j < 8; j++)
		{
			digest[j] ^= digest_tmp[j];
		}

		data += 64u;
	}
}
/**
 * @brief Initializes the SM3 context structure.
 *
 */
void peace_sm3_Init(peace_sm3_CTX *ctx)
{
	memset(ctx, 0, sizeof(*ctx));
	ctx->digest[0] = 0x7380166FU;
	ctx->digest[1] = 0x4914B2B9U;
	ctx->digest[2] = 0x172442D7U;
	ctx->digest[3] = 0xDA8A0600U;
	ctx->digest[4] = 0xA96F30BCU;
	ctx->digest[5] = 0x163138AAU;
	ctx->digest[6] = 0xE38DEE4DU;
	ctx->digest[7] = 0xB0FB0E4EU;
}
/**
 * @brief Updates the SM3 hash computation with additional data.
 *
 */
void peace_sm3_update(peace_sm3_CTX *ctx, const uint8_t *data, size_t inlen)
{
	size_t blocks;
	size_t left;

	ctx->num &= 0x3f;
	if (ctx->num)
	{
		left = peace_sm3_BLOCK_SIZE - ctx->num;
		if (inlen < left)
		{
			memcpy(ctx->block + ctx->num, data, inlen);
			ctx->num += inlen;
			return;
		}
		else
		{
			memcpy(ctx->block + ctx->num, data, left);
			peace_sm3_compress_blocks(ctx->digest, ctx->block, 1);
			ctx->nblocks++;
			data += left;
			inlen -= left;
		}
	}

	blocks = inlen / peace_sm3_BLOCK_SIZE;
	if (blocks)
	{
		peace_sm3_compress_blocks(ctx->digest, data, blocks);
		ctx->nblocks += blocks;
		data += peace_sm3_BLOCK_SIZE * blocks;
		inlen -= peace_sm3_BLOCK_SIZE * blocks;
	}

	ctx->num = inlen;
	if (inlen)
	{
		memcpy(ctx->block, data, inlen);
	}
}
/**
 * @brief Finalizes the SM3 hash computation and outputs the resulting hash value.
 *
 */
void peace_sm3_finish(peace_sm3_CTX *ctx, uint8_t *digest)
{
	int i;

	ctx->num &= 0x3fu;
	ctx->block[ctx->num] = 0x80u;

	if (ctx->num <= peace_sm3_BLOCK_SIZE - 9)
	{
		memset(ctx->block + ctx->num + 1, 0, peace_sm3_BLOCK_SIZE - ctx->num - 9);
	}
	else
	{
		memset(ctx->block + ctx->num + 1, 0, peace_sm3_BLOCK_SIZE - ctx->num - 1);
		peace_sm3_compress_blocks(ctx->digest, ctx->block, 1);
		memset(ctx->block, 0, peace_sm3_BLOCK_SIZE - 8);
	}

	PUTU32(ctx->block + 56, ctx->nblocks >> 23);
	PUTU32(ctx->block + 60, (ctx->nblocks << 9) + (ctx->num << 3));

	peace_sm3_compress_blocks(ctx->digest, ctx->block, 1);

	for (i = 0; i < 8; i++)
	{
		PUTU32(digest + i * 4, ctx->digest[i]);
	}
}
/**
 * @brief Computes the SM3 hash of a single data chunk.
 *
 */
void peace_sm3_digest(const uint8_t *msg, size_t msglen,
					  uint8_t dgst[peace_sm3_DIGEST_SIZE])
{
	peace_sm3_CTX ctx;
	peace_sm3_Init(&ctx);
	peace_sm3_update(&ctx, msg, msglen);
	peace_sm3_finish(&ctx, dgst);
	memset(&ctx, 0, sizeof(ctx));
}
/**
 * @brief Finalizes the SM3 hash computation and outputs the resulting hash value.
 *
 */
int peace_asn1_length_to_der(size_t len, uint8_t **out, size_t *outlen)
{
	uint8_t buf[4];
	int nbytes;
	if (len > INT_MAX)
	{
		return E_ERROR;
	}
	if (!outlen)
	{
		return E_ERROR;
	}

	if (len < 128)
	{
		if (out && *out)
		{
			(*out)[0] = (uint8_t)len;
			(*out)++;
		}
		(*outlen)++;
	}
	else
	{

		if (len < 256U)
			nbytes = 1;
		else if (len < 65536U)
			nbytes = 2;
		else if (len < (1U << 24))
			nbytes = 3;
		else
			nbytes = 4;
		PUTU32(buf, (uint32_t)len);

		if (out && *out)
		{
			(*out)[0] = 0x80U + (uint8_t)nbytes;
			memcpy(&(*out)[1], &buf[4U - (uint8_t)nbytes], (size_t)nbytes);
			(*out) += 1U + (uint8_t)nbytes;
		}
		(*outlen) += 1U + (size_t)nbytes;
	}
	return 1;
}
/**
 * @brief Computes the SM3 hash of a single data chunk.
 *
 */
int peace_asn1_length_from_der(size_t *len, const uint8_t **in, size_t *inlen)
{
	if (!len || !in || !(*in) || !inlen)
	{

		return E_ERROR;
	}

	if (*inlen == 0)
	{

		return E_ERROR;
	}

	if (**in < 128)
	{
		*len = *(*in)++;
		(*inlen)--;
	}
	else
	{
		uint8_t buf[4] = {0};
		int nbytes = *(*in)++ & 0x7f;
		(*inlen)--;

		if (nbytes < 1 || nbytes > 4)
		{

			return E_ERROR;
		}
		if (*inlen < nbytes)
		{

			return E_ERROR;
		}

		memcpy(buf + 4 - nbytes, *in, nbytes);
		*len = (size_t)GETU32(buf);
		*in += nbytes;
		*inlen -= nbytes;
	}

	if (*inlen < *len)
	{

		return -2;
	}
	return 1;
}
/**
 * @brief Compresses multiple blocks of data using the SM3 compression function.
 *
 */
int peace_asn1_header_to_der(int tag, size_t dlen, uint8_t **out, size_t *outlen)
{
	if (!outlen)
	{

		return E_ERROR;
	}

	if (out && *out)
	{
		*(*out)++ = (uint8_t)tag;
	}
	(*outlen)++;

	(void)peace_asn1_length_to_der(dlen, out, outlen);
	return 1;
}
/**
 * @brief Converts an integer to DER-encoded format.
 *
 */
int peace_asn1_type_to_der(int tag, const uint8_t *d, size_t dlen, uint8_t **out, size_t *outlen)
{
	int length_result;
	if (!outlen)
	{
		return E_ERROR;
	}

	if (!d)
	{
		if (dlen)
		{
			return E_ERROR;
		}
		return 0;
	}

	// Write the tag to the output buffer
	if (out && *out)
	{
		*(*out)++ = (uint8_t)tag;
	}
	(*outlen)++;

	// Write the length to the output buffer
	length_result = peace_asn1_length_to_der(dlen, out, outlen);
	if (length_result != 1)
	{
		return length_result; // Propagate error from peace_asn1_length_to_der
	}

	// Write the data to the output buffer
	if (out && *out)
	{
		memcpy(*out, d, dlen);
		*out += dlen;
	}
	*outlen += dlen;

	return 1;
}
/**
 * @brief Encodes a header to DER format.
 *
 */
int peace_asn1_type_from_der(int tag, const uint8_t **d, size_t *dlen, const uint8_t **in, size_t *inlen)
{
	if (!d || !dlen || !in || !(*in) || !inlen)
	{
		return E_ERROR;
	}

	if (*inlen == 0 || **in != tag)
	{
		*d = NULL;
		*dlen = 0;
		return 0;
	}
	(*in)++;
	(*inlen)--;

	if (peace_asn1_length_from_der(dlen, in, inlen) != 1)
	{
		return E_ERROR;
	}

	*d = *in;
	*in += *dlen;
	*inlen -= *dlen;
	return 1;
}
/**
 * @brief Decodes a type from DER format.
 *
 */
int peace_asn1_integer_to_der_ex(int tag, const uint8_t *a, size_t alen, uint8_t **out, size_t *outlen)
{
	if (!outlen)
	{
		return E_ERROR;
	}

	if (!a)
	{
		return 0;
	}
	if (alen <= 0 || alen > INT_MAX)
	{

		return E_ERROR;
	}

	if (out && *out)
		*(*out)++ = tag;
	(*outlen)++;

	while (*a == 0 && alen > 1)
	{
		a++;
		alen--;
	}

	if (a[0] & 0x80)
	{
		peace_asn1_length_to_der(alen + 1, out, outlen);
		if (out && *out)
		{
			*(*out)++ = 0x00;
			memcpy(*out, a, alen);
			(*out) += alen;
		}
		(*outlen) += 1 + alen;
	}
	else
	{
		peace_asn1_length_to_der(alen, out, outlen);
		if (out && *out)
		{
			memcpy(*out, a, alen);
			(*out) += alen;
		}
		(*outlen) += alen;
	}

	return 1;
}
/**
 * @brief Decodes an integer from DER format.
 *
 */
int peace_asn1_integer_from_der_ex(int tag, const uint8_t **a, size_t *alen, const uint8_t **in, size_t *inlen)
{
    if (!a || !alen || !in || !(*in) || !inlen) {
        return E_ERROR;
    }

    // Check if the input length is zero or the tag does not match
    if (*inlen == 0 || **in != tag) {
        *a = NULL;
        *alen = 0;
        return 0;
    }

    // Move to the next byte after the tag
    (*in)++;
    (*inlen)--;

    size_t length;
    // Parse the length of the integer from DER
    if (peace_asn1_length_from_der(&length, in, inlen) != 1) {
        return E_ERROR;
    }

    // Check if the length is zero
    if (length == 0) {
        return E_ERROR;
    }

    // Check if the most significant bit is set (negative number)
    if (**in & 0x80) {
        return E_ERROR;
    }

    // Check for leading zeros
    if (**in == 0 && length > 1) {
        (*in)++;
        (*inlen)--;
        length--;

        // Ensure the next byte is not zero and the most significant bit is set
        if (((**in) & 0x80) == 0) {
            return E_ERROR;
        }
    }

    // Set the output pointer and length
    *a = *in;
    *alen = length;
    *in += length;
    *inlen -= length;

    return 1;
}
/**
 * @brief Converts a type to DER format.
 *
 */
int peace_asn1_int_from_der_ex(int tag, int *a, const uint8_t **in, size_t *inlen)
{
    if (!a || !in || !(*in) || !inlen) {
        return E_ERROR;
    }

    const uint8_t *p;
    size_t len;
    int ret;

    // Parse the ASN.1 integer from DER
    ret = peace_asn1_integer_from_der_ex(tag, &p, &len, in, inlen);
    if (ret != 1) {
        if (ret < 0) {
            // Handle specific error cases if needed
        } else {
            *a = -1;
        }
        return ret;
    }

    // Check if the length of the integer exceeds the size of int
    if (len > sizeof(*a)) {
        return E_ERROR;
    }

    // Convert the byte array to an integer
    *a = 0;
    for (size_t i = 0; i < len; i++) {
        *a = (*a << 8) | p[i];
    }

    // Check if the resulting integer is negative
    if (*a < 0) {
        return E_ERROR;
    }

    return 1;
}
/**
 * @brief Checks if a length is zero.
 *
 */
int peace_asn1_check(int expr)
{
	if (expr)
		return 1;

	return E_ERROR;
}
/**
 * @brief Checks if one length is less than or equal to another.
 *
 */
int peace_asn1_length_is_zero(size_t len)
{
	if (len)
	{
		return E_ERROR;
	}
	return 1;
}
/**
 * @brief Evaluates an expression and asserts its truthiness.
 *
 */
int peace_asn1_length_le(size_t len1, size_t len2)
{
	if (len1 > len2)
	{
		return E_ERROR;
	}
	return 1;
}
/**
 * @brief Performs bitwise XOR operation between two memory buffers and stores the result in another buffer.
 *
 */
void peace_sm2_memxor(void *r, const void *a, const void *b, size_t len)
{
    uint8_t *pr = (uint8_t *)r;
    const uint8_t *pa = (const uint8_t *)a;
    const uint8_t *pb = (const uint8_t *)b;

    for (size_t i = 0; i < len; i++) {
        pr[i] = pa[i] ^ pb[i];
    }
}
